using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace DbView
{
	// interface to monitor actions happening on the 
	// For V1 only one observer permitted
	public interface ITabObserver
	{
		void TabAdded(String tag, object userData);
		void TabActivated(String tag, object userData, object prevUserData);
		bool IsTabOkToLeave(String tag, object userData);
		bool IsTabOkToRemove(String tag, object userData);
		void TabRemoved(String tag, object userData, bool IsActive);
		void TabMoved(String tag, object userData, int oldIndex, int newIndex);
		// add context menu extensions if necessary
	}

	public class TabCollection : ITabObserver  // ought to implement IEnumerable
	{
		public TabCollection(ToolStrip parent)
		{
			this.m_Parent = parent;
			this.ConfigureTabContextMenu();
			this.SetObserver(null); // 
			// radical
			// this.m_Visibility = visibility.one;
			this.SetVisible();

#if DEBUG==true
			this.m_bObserve = true;
#endif
		}

		private void ConfigureTabContextMenu()
		{
			this.menuGridButtons = new ContextMenu();
			this.miCloseThis = new MenuItem("Close", this.miCloseThis_Click);
			this.miCloseAllButThis = new MenuItem("Close all but This", this.miCloseAllButThis_Click);
			this.miMoveToStart = new MenuItem("Move to start", this.miMoveToStart_Click);
			this.miMoveToEnd = new MenuItem("Move to end", this.miMoveToEnd_Click);
			this.menuGridButtons.MenuItems.Add(this.miCloseThis);
			this.menuGridButtons.MenuItems.Add(this.miCloseAllButThis);
			this.menuGridButtons.MenuItems.Add(this.miMoveToStart);
			this.menuGridButtons.MenuItems.Add(this.miMoveToEnd);
		}

		// caller must manage a couple of 16-bit images for this. Not Compulsory
		public void SetTabCloseImages(Image closeActive, Image closeInActive)
		{
			this.m_CloseActive = closeActive;
			this.m_CloseInActive = closeInActive;
		}
		public void SetTabCloseImage(Image close) { this.SetTabCloseImages(close, close); }  // apply single image for active/inactive state

		public void SetObserver(ITabObserver observer)
		{
			this.m_Observer = (observer == null) ? this : observer; // observe self if none set
		}

		public void SetVisibilityMode(Visibility vs)
		{
			this.m_Visibility = vs;
			this.SetVisible();
		}

		public ToolStripItem ActiveTab { get { return this.m_ActiveBtn; } }

		/////////////////////////////////////////////////////////////////////////
		// ITabObserver interface implementation
		public void TabAdded(String tag, object userData)
		{
			if (this.m_bObserve) Console.WriteLine("TabAdded {0}", tag);
		}
		public void TabActivated(String tag, object userData, object prevUserData)
		{
			if (this.m_bObserve) Console.WriteLine("TabActivated {0}", tag);
		}
		public bool IsTabOkToLeave(String tag, object userData)
		{
			return true; // false if eg dirty data
		}
		public bool IsTabOkToRemove(String tag, object userData)
		{
			return true; // false if eg dirty data
		}
		public void TabRemoved(String tag, object userData, bool IsActive)
		{
			if (this.m_bObserve) Console.WriteLine("TabRemoved {0}", tag);
		}
		public void TabMoved(String tag, object userData, int oldIndex, int newIndex)
		{
			if (this.m_bObserve) Console.WriteLine("TabMoved {0} from {1} to {2}", tag, oldIndex, newIndex);
		}
		//
		/////////////////////////////////////////////////////////////////////////

		// 
		// Adds a new button 
		//
		public void AddTab(String tag, string text, object userData, bool select)
		{
			// adjust tag if exists
			tag = this.CheckAdjustTag(tag);
			ToolStripItem btn = this.makeNewLabel(tag, text);
			btn.Tag = userData;
			this.m_Parent.Items.Add(btn);
			this.m_Tabs.Add(btn);
			this.SetVisible();
			if (select)
				this.SetActive(btn);
			this.m_Observer.TabAdded(btn.Name, userData);
		}

		// Inserts a new tab to the right of the selected tab
		public void InsertTabAfterSelected(String tag, string text, object userData, bool select)
		{
			// find the location of selected tab.
			int iPos = this.m_Tabs.IndexOf(this.m_ActiveBtn);

			// adjust tag if exists
			tag = this.CheckAdjustTag(tag);
			ToolStripItem btn = this.makeNewLabel(tag, text);
			btn.Tag = userData;

			this.m_Parent.Items.Insert(iPos + 1, btn);
			this.m_Tabs.Insert(iPos + 1, btn);

			this.SetVisible();
			if (select)
				this.SetActive(btn);
			this.m_Observer.TabAdded(btn.Name, userData);
		}

		public void RemoveTab(ToolStripItem btn, ToolStripItem newActive)
		{
			// abort if observer says no!
			if (!this.m_Observer.IsTabOkToRemove(btn.Name, btn.Tag)) return;
			// Check whether or not removing active
			bool RemovingActive = (this.m_ActiveBtn == btn);
			// hold the position to calculate the tab to set active if removing the active one.
			int posInList = (RemovingActive) ? this.m_Tabs.IndexOf(btn) : -1;
			////////////////////////////////////////////////////////////
			// change the tab collection state
			this.m_Tabs.Remove(btn);
			this.m_Parent.Items.Remove(btn);

			// capture tab data in case of memory issues
			string tag = btn.Name;
			object userData = btn.Tag;
			// probably not necessary
			btn.Tag = null;
			btn.Dispose();

			// Inform the observer
			this.m_Observer.TabRemoved(btn.Name, userData, RemovingActive);

			this.SetVisible();
			// if this was current make another current
			if (RemovingActive && this.m_Tabs.Count > 0) // proxy test for not selected. Not if lasy tab was removed.
			{
				if (newActive != null && this.m_Tabs.IndexOf(newActive) != -1)
					this.SetActive(newActive);
				else
				{
					// if was last then back one
					if (posInList >= this.m_Tabs.Count) --posInList;
					this.SetActive(this.m_Tabs[posInList]);
				}
			}
		}
		public void RemoveTab(ToolStripItem btn) { this.RemoveTab(btn, null); }

		public ToolStripItem FindUserData(object tgtUserData)
		{
			foreach (ToolStripItem btn in this.m_Tabs)
			{
				if (btn.Tag == tgtUserData) return btn;
			}
			return null;
		}

		//
		// in case duplicate tabs cause problems.
		//
		private string CheckAdjustTag(string tag)
		{
			// iterate existing labels find highest version
			// adjust
			int seq = 0;
			foreach (ToolStripItem btn in this.m_Tabs)
			{
				if (btn.Name == tag)
					++seq;
			}
			if (seq > 0) tag = string.Format("{0}:{1}", tag, seq);
			return tag; // 
		}

		//
		// Construct a new button and customise it's appearence
		//
		private ToolStripItem makeNewLabel(String tag, string text)
		{
			ToolStripButton btn = new ToolStripButton(text);
			btn.Name = tag;
			// want text and image buttons
			btn.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.ImageAndText;

			int[] margins = this.getMarginInfo(); // margins depend on selected rendermode
			// slim margin between buttons 2 pixels
			btn.Margin = new System.Windows.Forms.Padding(margins[0], margins[1], margins[2], margins[3]);
			// grey - not super background
			btn.BackColor = System.Drawing.SystemColors.Control;
			// Deal with the image
			btn.Image = this.m_CloseInActive;
			btn.ImageTransparentColor = System.Drawing.Color.Magenta;
			btn.TextImageRelation = System.Windows.Forms.TextImageRelation.TextBeforeImage;

			// Wire up events
			btn.Click += new EventHandler(this.Button_Click);
			btn.MouseDown += new MouseEventHandler(this.Button_MouseDown);

			return btn;

		}

		//
		// In case we need to fall back to the less preferred renderer
		//
		private int[] getMarginInfo()
		{
			int[] margins = new int[4] { 0, 0, 0, 0 };
			if (this.m_Parent.RenderMode != ToolStripRenderMode.System)
			{
				margins[0] = margins[1] = margins[2] = margins[3] = 1;
				margins[3] = 0;
			}
			return margins;
		}

		// 
		public void SetActive(ToolStripItem tgtBtn)
		{
			foreach (ToolStripItem btn in this.m_Tabs)
			{
				this.SetStyle(btn, (btn == tgtBtn) ? LabelStyle.active : LabelStyle.inactive);
			}
			// defend against spurious activation messages
			if (this.m_ActiveBtn != tgtBtn)
			{
				object prevUserData = (this.m_ActiveBtn == null) ? null : this.m_ActiveBtn.Tag;
				this.m_ActiveBtn = tgtBtn;
				this.m_Observer.TabActivated(tgtBtn.Name, tgtBtn.Tag, prevUserData);
			}
		}

		public void NavigateTabs(Direction dir)
		{
			int p, n, c;
			p = n = c = 0;
			// get previous and next in a cyclying manner 
			for (int idx = 0; idx < this.m_Tabs.Count; ++idx)
			{
				ToolStripItem btn = this.m_Tabs[idx];
				if (btn == this.m_ActiveBtn)
				{
					c = idx;
					p = (idx == 0) ? this.m_Tabs.Count - 1 : idx - 1;
					n = (idx == this.m_Tabs.Count - 1) ? 0 : idx + 1;
				}
			}
			c = (dir == Direction.forward) ? n :
				(dir == Direction.back) ? p : c;
			this.SetActive(this.m_Tabs[c]);
		}

		private void SetStyle(ToolStripItem btn, LabelStyle style)
		{
			int bm = 0;
			if (this.m_Parent.RenderMode == ToolStripRenderMode.System)
			{
				switch (style)
				{
					case LabelStyle.inactive:
						btn.BackColor = System.Drawing.SystemColors.InactiveCaption;
						btn.ForeColor = System.Drawing.SystemColors.InactiveCaptionText;
						btn.Image = this.m_CloseInActive;
						bm = 1;
						break;
					case LabelStyle.active:
						btn.BackColor = System.Drawing.SystemColors.ActiveCaption;
						btn.ForeColor = System.Drawing.SystemColors.ActiveCaptionText;
						btn.Image = this.m_CloseActive;
						bm = 0;
						break;
				}
			}
			else
			{
				switch (style)
				{
					case LabelStyle.inactive:
						btn.ForeColor = System.Drawing.SystemColors.ControlText;
						btn.BackColor = System.Drawing.SystemColors.Control;
						// new style: I prefer. Too late to introduce now.
						btn.ForeColor = System.Drawing.Color.Gray;
						btn.BackColor = System.Drawing.Color.Black;
						btn.Image = this.m_CloseInActive;
						bm = 2;
						break;
					case LabelStyle.active:
						btn.ForeColor = System.Drawing.SystemColors.ControlText;
						if (this.m_CloseActive == this.m_CloseInActive)
							btn.BackColor = System.Drawing.SystemColors.Info;
						else
							//btn.BackColor = System.Drawing.SystemColors.Control;
							btn.BackColor = System.Drawing.SystemColors.Info;
						btn.Image = this.m_CloseActive;
						bm = 0;
						break;
				}
			}

			int[] margins = this.getMarginInfo(); // margins depend on selected rendermode
			btn.Margin = new System.Windows.Forms.Padding(margins[0], margins[1], margins[2], margins[3] + bm);
		}

		//////////////////////////////////////////////////////////////////////////
		// Button management ops
		private void SetVisible()
		{
			int tabCount = this.m_Tabs.Count;
			bool visible = (this.m_Visibility == Visibility.always) ? true :
						   (this.m_Visibility == Visibility.never) ? false :
						   (this.m_Visibility == Visibility.one) ? (tabCount > 0) :
						   (this.m_Visibility == Visibility.many) ? (tabCount > 1) : true;
			this.m_Parent.Visible = visible;
		}

		private int MoveTo(ToolStripItem btn, int newPos)  // 0 only supported
		{
			// assumes no other items on the toolbar eg. add button
			int currentPos = -1;
			for (int idx = 0; idx < this.m_Parent.Items.Count; ++idx)
			{
				if (this.m_Parent.Items[idx] == btn) currentPos = idx;
			}
			if (currentPos == newPos) return currentPos;  // return the old pos why not?
			// apply move to end logic
			if (newPos == -1)
				newPos = this.m_Tabs.Count - 1;
			if (currentPos == newPos) return currentPos;  // return the old pos why not?
			// do it.
			this.m_Parent.Items.Remove(btn);
			this.m_Parent.Items.Insert(newPos, btn);
			this.m_Tabs.Remove(btn);
			this.m_Tabs.Insert(newPos, btn);

			this.m_Observer.TabMoved(btn.Name, btn.Tag, currentPos, newPos);

			return currentPos;
		}
		private int MoveToEnd(ToolStripItem btn) { return this.MoveTo(btn, -1); }

		/////////////////////////////////////////////////////////////////////////////////
		// Events
		// Clicking a button needs to distinguish cases:
		// 1. left-click on text -> make active
		// 2. left-click on image -> remove the button
		// 3. right-click -> popup contect menu
		//
		private void Button_MouseDown(object sender, MouseEventArgs e)
		{
			if (e.Button == MouseButtons.Left)
			{
				this.m_buttonHitX = e.X;
			}
			else if (e.Button == MouseButtons.Right)
			{
				int offsetX = (sender as ToolStripItem).Bounds.Left;
				this.m_UnderPopup = sender as ToolStripItem;
				this.menuGridButtons.Show(this.m_Parent, new Point(offsetX + e.X, e.Y));
			}
			this.m_LastPress = e.Button;
		}

		private void Button_Click(object sender, EventArgs e)
		{
			if (this.m_LastPress == MouseButtons.Left)
			{
				ToolStripButton btn = sender as ToolStripButton;
				bool bHitBitMap = (btn.ContentRectangle.Right - this.m_buttonHitX < 16) ? true : false;
				if (btn.Image == null) bHitBitMap = false; // not bitmap case
				if (bHitBitMap)
				{
					// do the close think
					this.RemoveTab(btn);
				}
				else
				{
					if (this.m_ActiveBtn != null)
						// below should really be IsTabOkToLeave, but for now we'll use this
						if (!this.m_Observer.IsTabOkToLeave(this.m_ActiveBtn.Name, this.m_ActiveBtn.Tag)) return;
					this.SetActive(btn);
					if (btn.IsOnOverflow) // button is off the end
						this.MoveTo(btn, 0);
					// notify
				}
			}
		}

		/////////////////////////////////////////////////////////////////////////////////
		// Context menu 
		// 
		private void miCloseAllButThis_Click(object sender, EventArgs e)
		{
			// does
			List<ToolStripItem> toRemove = new List<ToolStripItem>();
			foreach (ToolStripItem btn in this.m_Tabs)
			{
				if (btn != this.m_UnderPopup)
				{
					if (this.m_Observer.IsTabOkToRemove(btn.Name, btn.Tag))
						toRemove.Add(btn);
				}
			}
			int idx = toRemove.Count;
			while (idx-- > 0)
			{
				ToolStripItem btn = toRemove[idx];
				toRemove.Remove(btn);
				this.RemoveTab(btn);
			}
			this.SetActive(this.m_UnderPopup);
		}

		private void miCloseThis_Click(object sender, EventArgs e)
		{
			ToolStripItem btn = this.m_UnderPopup;
			this.RemoveTab(btn);
		}

		private void miMoveToEnd_Click(object sender, EventArgs e)
		{
			ToolStripItem btn = this.m_UnderPopup;
			MoveToEnd(btn);
		}

		private void miMoveToStart_Click(object sender, EventArgs e)
		{
			ToolStripItem btn = this.m_UnderPopup;
			this.MoveToStart(btn);
		}

		public void MoveToStart(ToolStripItem btn)
		{
			this.MoveTo(btn, 0);
		}

		//////////////////////////////////////////////////////////////////////////////////
		// properties
		// tab styles
		enum LabelStyle { inactive, active }
		// parent visibility mode
		public enum Visibility { always, never, one, many }
		// nvigation
		public enum Direction { none, forward, back }


		ToolStrip m_Parent;		// supplied by the caller the toolbar host for the buttons
		ITabObserver m_Observer; // interface for calling back when tabs change
		Image m_CloseActive;   // supplied by caller the close icon for the active button
		Image m_CloseInActive; // supplied by caller the close icon for the inactive buttons
		Visibility m_Visibility = Visibility.always;

		// Tab popup defined. Actually its not really needed
		ContextMenu menuGridButtons;
		MenuItem miCloseThis;
		MenuItem miCloseAllButThis;
		MenuItem miMoveToStart;
		MenuItem miMoveToEnd;

		// The tabs. In this implementation they at ToolStripButtons
		List<ToolStripItem> m_Tabs = new List<ToolStripItem>();

		// state
		ToolStripItem m_ActiveBtn = null;	// The currently selected item. (could be inferred from state)
		// bool this.m_RightClick = false;			// last mouse button detected on mouse down capture
		MouseButtons m_LastPress;			// last mouse button detected on mouse down capture
		int m_buttonHitX;					// exact X position of mouse down click for button/bitmap hit detection
		ToolStripItem m_UnderPopup = null;	// hold the button that was hit in the mouse down
		// debug
		bool m_bObserve = false;			// in release do not emit messages

	}
}
